/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactCompositeComponent
 */

"use strict";

var _assign = require("object-assign");

var ReactComponentEnvironment = require("./ReactComponentEnvironment");
var ReactCurrentOwner = require("./ReactCurrentOwner");
var ReactElement = require("./ReactElement");
var ReactErrorUtils = require("./ReactErrorUtils");
var ReactInstanceMap = require("./ReactInstanceMap");
var ReactInstrumentation = require("./ReactInstrumentation");
var ReactNodeTypes = require("./ReactNodeTypes");
var ReactPerf = require("./ReactPerf");
var ReactPropTypeLocations = require("./ReactPropTypeLocations");
var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");
var ReactReconciler = require("./ReactReconciler");
var ReactUpdateQueue = require("./ReactUpdateQueue");

var emptyObject = require("fbjs/lib/emptyObject");
var invariant = require("fbjs/lib/invariant");
var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
var warning = require("fbjs/lib/warning");

function getDeclarationErrorAddendum(component) {
  var owner = component._currentElement._owner || null;
  if (owner) {
    var name = owner.getName();
    if (name) {
      return " Check the render method of `" + name + "`.";
    }
  }
  return "";
}

function StatelessComponent(Component) {}
StatelessComponent.prototype.render = function () {
  var Component = ReactInstanceMap.get(this)._currentElement.type;
  var element = Component(this.props, this.context, this.updater);
  warnIfInvalidElement(Component, element);
  return element;
};

function warnIfInvalidElement(Component, element) {
  if (process.env.NODE_ENV !== "production") {
    process.env.NODE_ENV !== "production"
      ? warning(
          element === null ||
            element === false ||
            ReactElement.isValidElement(element),
          "%s(...): A valid React element (or null) must be returned. You may have " +
            "returned undefined, an array or some other invalid object.",
          Component.displayName || Component.name || "Component",
        )
      : void 0;
  }
}

/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]
 *     - [children's componentWillMount and render]
 *     - [children's componentDidMount]
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------
 */

/**
 * An incrementing ID assigned to each component when it is mounted. This is
 * used to enforce the order in which `ReactUpdates` updates dirty components.
 *
 * @private
 */
var nextMountID = 1;

/**
 * @lends {ReactCompositeComponent.prototype}
 */
var ReactCompositeComponentMixin = {
  /**
   * Base constructor for all composite component.
   *
   * @param {ReactElement} element
   * @final
   * @internal
   */
  construct: function (element) {
    this._currentElement = element;
    this._rootNodeID = null;
    this._instance = null;
    this._nativeParent = null;
    this._nativeContainerInfo = null;

    // See ReactUpdateQueue
    this._pendingElement = null;
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    this._renderedNodeType = null;
    this._renderedComponent = null;
    this._context = null;
    this._mountOrder = 0;
    this._topLevelWrapper = null;

    // See ReactUpdates and ReactUpdateQueue.
    this._pendingCallbacks = null;
  },

  /**
   * Initializes the component, renders markup, and registers event listeners.
   *
   * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
   * @param {?object} nativeParent
   * @param {?object} nativeContainerInfo
   * @param {?object} context
   * @return {?string} Rendered markup to be inserted into the DOM.
   * @final
   * @internal
   */
  mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context,
  ) {
    this._context = context;
    this._mountOrder = nextMountID++;
    this._nativeParent = nativeParent;
    this._nativeContainerInfo = nativeContainerInfo;

    var publicProps = this._processProps(this._currentElement.props);
    var publicContext = this._processContext(context);

    var Component = this._currentElement.type;

    // Initialize the public class
    var inst;
    var renderedElement;

    if (Component.prototype && Component.prototype.isReactComponent) {
      if (process.env.NODE_ENV !== "production") {
        ReactCurrentOwner.current = this;
        try {
          inst = new Component(publicProps, publicContext, ReactUpdateQueue);
        } finally {
          ReactCurrentOwner.current = null;
        }
      } else {
        inst = new Component(publicProps, publicContext, ReactUpdateQueue);
      }
    } else {
      if (process.env.NODE_ENV !== "production") {
        ReactCurrentOwner.current = this;
        try {
          inst = Component(publicProps, publicContext, ReactUpdateQueue);
        } finally {
          ReactCurrentOwner.current = null;
        }
      } else {
        inst = Component(publicProps, publicContext, ReactUpdateQueue);
      }
      if (inst == null || inst.render == null) {
        renderedElement = inst;
        warnIfInvalidElement(Component, renderedElement);
        !(inst === null || inst === false || ReactElement.isValidElement(inst))
          ? process.env.NODE_ENV !== "production"
            ? invariant(
                false,
                "%s(...): A valid React element (or null) must be returned. You may have " +
                  "returned undefined, an array or some other invalid object.",
                Component.displayName || Component.name || "Component",
              )
            : invariant(false)
          : void 0;
        inst = new StatelessComponent(Component);
      }
    }

    if (process.env.NODE_ENV !== "production") {
      // This will throw later in _renderValidatedComponent, but add an early
      // warning now to help debugging
      if (inst.render == null) {
        process.env.NODE_ENV !== "production"
          ? warning(
              false,
              "%s(...): No `render` method found on the returned component " +
                "instance: you may have forgotten to define `render`.",
              Component.displayName || Component.name || "Component",
            )
          : void 0;
      }

      var propsMutated = inst.props !== publicProps;
      var componentName =
        Component.displayName || Component.name || "Component";

      process.env.NODE_ENV !== "production"
        ? warning(
            inst.props === undefined || !propsMutated,
            "%s(...): When calling super() in `%s`, make sure to pass " +
              "up the same props that your component's constructor was passed.",
            componentName,
            componentName,
          )
        : void 0;
    }

    // These should be set up in the constructor, but as a convenience for
    // simpler class abstractions, we set them up after the fact.
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = ReactUpdateQueue;

    this._instance = inst;

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);

    if (process.env.NODE_ENV !== "production") {
      // Since plain JS classes are defined without any special initialization
      // logic, we can not catch common errors early. Therefore, we have to
      // catch them here, at initialization time, instead.
      process.env.NODE_ENV !== "production"
        ? warning(
            !inst.getInitialState || inst.getInitialState.isReactClassApproved,
            "getInitialState was defined on %s, a plain JavaScript class. " +
              "This is only supported for classes created using React.createClass. " +
              "Did you mean to define a state property instead?",
            this.getName() || "a component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            !inst.getDefaultProps || inst.getDefaultProps.isReactClassApproved,
            "getDefaultProps was defined on %s, a plain JavaScript class. " +
              "This is only supported for classes created using React.createClass. " +
              "Use a static property to define defaultProps instead.",
            this.getName() || "a component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            !inst.propTypes,
            "propTypes was defined as an instance property on %s. Use a static " +
              "property to define propTypes instead.",
            this.getName() || "a component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            !inst.contextTypes,
            "contextTypes was defined as an instance property on %s. Use a " +
              "static property to define contextTypes instead.",
            this.getName() || "a component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            typeof inst.componentShouldUpdate !== "function",
            "%s has a method called " +
              "componentShouldUpdate(). Did you mean shouldComponentUpdate()? " +
              "The name is phrased as a question because the function is " +
              "expected to return a value.",
            this.getName() || "A component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            typeof inst.componentDidUnmount !== "function",
            "%s has a method called " +
              "componentDidUnmount(). But there is no such lifecycle method. " +
              "Did you mean componentWillUnmount()?",
            this.getName() || "A component",
          )
        : void 0;
      process.env.NODE_ENV !== "production"
        ? warning(
            typeof inst.componentWillRecieveProps !== "function",
            "%s has a method called " +
              "componentWillRecieveProps(). Did you mean componentWillReceiveProps()?",
            this.getName() || "A component",
          )
        : void 0;
    }

    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }
    !(typeof initialState === "object" && !Array.isArray(initialState))
      ? process.env.NODE_ENV !== "production"
        ? invariant(
            false,
            "%s.state: must be set to an object or null",
            this.getName() || "ReactCompositeComponent",
          )
        : invariant(false)
      : void 0;

    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    var markup;
    if (inst.unstable_handleError) {
      markup = this.performInitialMountWithErrorHandling(
        renderedElement,
        nativeParent,
        nativeContainerInfo,
        transaction,
        context,
      );
    } else {
      markup = this.performInitialMount(
        renderedElement,
        nativeParent,
        nativeContainerInfo,
        transaction,
        context,
      );
    }

    if (inst.componentDidMount) {
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
  },

  performInitialMountWithErrorHandling: function (
    renderedElement,
    nativeParent,
    nativeContainerInfo,
    transaction,
    context,
  ) {
    var markup;
    var checkpoint = transaction.checkpoint();
    try {
      markup = this.performInitialMount(
        renderedElement,
        nativeParent,
        nativeContainerInfo,
        transaction,
        context,
      );
    } catch (e) {
      // Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
      transaction.rollback(checkpoint);
      this._instance.unstable_handleError(e);
      if (this._pendingStateQueue) {
        this._instance.state = this._processPendingState(
          this._instance.props,
          this._instance.context,
        );
      }
      checkpoint = transaction.checkpoint();

      this._renderedComponent.unmountComponent(true);
      transaction.rollback(checkpoint);

      // Try again - we've informed the component about the error, so they can render an error message this time.
      // If this throws again, the error will bubble up (and can be caught by a higher error boundary).
      markup = this.performInitialMount(
        renderedElement,
        nativeParent,
        nativeContainerInfo,
        transaction,
        context,
      );
    }
    return markup;
  },

  performInitialMount: function (
    renderedElement,
    nativeParent,
    nativeContainerInfo,
    transaction,
    context,
  ) {
    var inst = this._instance;
    if (inst.componentWillMount) {
      inst.componentWillMount();
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingStateQueue` without triggering a re-render.
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

    // If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedComponent = this._instantiateReactComponent(renderedElement);

    var markup = ReactReconciler.mountComponent(
      this._renderedComponent,
      transaction,
      nativeParent,
      nativeContainerInfo,
      this._processChildContext(context),
    );

    return markup;
  },

  getNativeNode: function () {
    return ReactReconciler.getNativeNode(this._renderedComponent);
  },

  /**
   * Releases any resources allocated by `mountComponent`.
   *
   * @final
   * @internal
   */
  unmountComponent: function (safely) {
    if (!this._renderedComponent) {
      return;
    }
    var inst = this._instance;

    if (inst.componentWillUnmount) {
      if (safely) {
        var name = this.getName() + ".componentWillUnmount()";
        ReactErrorUtils.invokeGuardedCallback(
          name,
          inst.componentWillUnmount.bind(inst),
        );
      } else {
        inst.componentWillUnmount();
      }
    }

    if (this._renderedComponent) {
      ReactReconciler.unmountComponent(this._renderedComponent, safely);
      this._renderedNodeType = null;
      this._renderedComponent = null;
      this._instance = null;
    }

    // Reset pending fields
    // Even if this component is scheduled for another update in ReactUpdates,
    // it would still be ignored because these fields are reset.
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;
    this._pendingCallbacks = null;
    this._pendingElement = null;

    // These fields do not really need to be reset since this object is no
    // longer accessible.
    this._context = null;
    this._rootNodeID = null;
    this._topLevelWrapper = null;

    // Delete the reference from the instance to this internal representation
    // which allow the internals to be properly cleaned up even if the user
    // leaks a reference to the public instance.
    ReactInstanceMap.remove(inst);

    // Some existing components rely on inst.props even after they've been
    // destroyed (in event handlers).
    // TODO: inst.props = null;
    // TODO: inst.state = null;
    // TODO: inst.context = null;
  },

  /**
   * Filters the context object to only contain keys specified in
   * `contextTypes`
   *
   * @param {object} context
   * @return {?object}
   * @private
   */
  _maskContext: function (context) {
    var Component = this._currentElement.type;
    var contextTypes = Component.contextTypes;
    if (!contextTypes) {
      return emptyObject;
    }
    var maskedContext = {};
    for (var contextName in contextTypes) {
      maskedContext[contextName] = context[contextName];
    }
    return maskedContext;
  },

  /**
   * Filters the context object to only contain keys specified in
   * `contextTypes`, and asserts that they are valid.
   *
   * @param {object} context
   * @return {?object}
   * @private
   */
  _processContext: function (context) {
    var maskedContext = this._maskContext(context);
    if (process.env.NODE_ENV !== "production") {
      var Component = this._currentElement.type;
      if (Component.contextTypes) {
        this._checkPropTypes(
          Component.contextTypes,
          maskedContext,
          ReactPropTypeLocations.context,
        );
      }
    }
    return maskedContext;
  },

  /**
   * @param {object} currentContext
   * @return {object}
   * @private
   */
  _processChildContext: function (currentContext) {
    var Component = this._currentElement.type;
    var inst = this._instance;
    if (process.env.NODE_ENV !== "production") {
      ReactInstrumentation.debugTool.onBeginProcessingChildContext();
    }
    var childContext = inst.getChildContext && inst.getChildContext();
    if (process.env.NODE_ENV !== "production") {
      ReactInstrumentation.debugTool.onEndProcessingChildContext();
    }
    if (childContext) {
      !(typeof Component.childContextTypes === "object")
        ? process.env.NODE_ENV !== "production"
          ? invariant(
              false,
              "%s.getChildContext(): childContextTypes must be defined in order to " +
                "use getChildContext().",
              this.getName() || "ReactCompositeComponent",
            )
          : invariant(false)
        : void 0;
      if (process.env.NODE_ENV !== "production") {
        this._checkPropTypes(
          Component.childContextTypes,
          childContext,
          ReactPropTypeLocations.childContext,
        );
      }
      for (var name in childContext) {
        !(name in Component.childContextTypes)
          ? process.env.NODE_ENV !== "production"
            ? invariant(
                false,
                '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
                this.getName() || "ReactCompositeComponent",
                name,
              )
            : invariant(false)
          : void 0;
      }
      return _assign({}, currentContext, childContext);
    }
    return currentContext;
  },

  /**
   * Processes props by setting default values for unspecified props and
   * asserting that the props are valid. Does not mutate its argument; returns
   * a new props object with defaults merged in.
   *
   * @param {object} newProps
   * @return {object}
   * @private
   */
  _processProps: function (newProps) {
    if (process.env.NODE_ENV !== "production") {
      var Component = this._currentElement.type;
      if (Component.propTypes) {
        this._checkPropTypes(
          Component.propTypes,
          newProps,
          ReactPropTypeLocations.prop,
        );
      }
    }
    return newProps;
  },

  /**
   * Assert that the props are valid
   *
   * @param {object} propTypes Map of prop name to a ReactPropType
   * @param {object} props
   * @param {string} location e.g. "prop", "context", "child context"
   * @private
   */
  _checkPropTypes: function (propTypes, props, location) {
    // TODO: Stop validating prop types here and only use the element
    // validation.
    var componentName = this.getName();
    for (var propName in propTypes) {
      if (propTypes.hasOwnProperty(propName)) {
        var error;
        try {
          // This is intentionally an invariant that gets caught. It's the same
          // behavior as without this statement except with a better message.
          !(typeof propTypes[propName] === "function")
            ? process.env.NODE_ENV !== "production"
              ? invariant(
                  false,
                  "%s: %s type `%s` is invalid; it must be a function, usually " +
                    "from React.PropTypes.",
                  componentName || "React class",
                  ReactPropTypeLocationNames[location],
                  propName,
                )
              : invariant(false)
            : void 0;
          error = propTypes[propName](props, propName, componentName, location);
        } catch (ex) {
          error = ex;
        }
        if (error instanceof Error) {
          // We may want to extend this logic for similar errors in
          // top-level render calls, so I'm abstracting it away into
          // a function to minimize refactoring in the future
          var addendum = getDeclarationErrorAddendum(this);

          if (location === ReactPropTypeLocations.prop) {
            // Preface gives us something to blacklist in warning module
            process.env.NODE_ENV !== "production"
              ? warning(
                  false,
                  "Failed Composite propType: %s%s",
                  error.message,
                  addendum,
                )
              : void 0;
          } else {
            process.env.NODE_ENV !== "production"
              ? warning(
                  false,
                  "Failed Context Types: %s%s",
                  error.message,
                  addendum,
                )
              : void 0;
          }
        }
      }
    }
  },

  receiveComponent: function (nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;

    this._pendingElement = null;

    this.updateComponent(
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext,
    );
  },

  /**
   * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
   * is set, update the component.
   *
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context,
      );
    }

    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context,
      );
    }
  },

  /**
   * Perform an update to a mounted component. The componentWillReceiveProps and
   * shouldComponentUpdate methods are called, then (assuming the update isn't
   * skipped) the remaining update lifecycle methods are called and the DOM
   * representation is updated.
   *
   * By default, this implements React's rendering and reconciliation algorithm.
   * Sophisticated clients may wish to override this.
   *
   * @param {ReactReconcileTransaction} transaction
   * @param {ReactElement} prevParentElement
   * @param {ReactElement} nextParentElement
   * @internal
   * @overridable
   */
  updateComponent: function (
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext,
  ) {
    var inst = this._instance;
    var willReceive = false;
    var nextContext;
    var nextProps;

    // Determine if the context has changed or not
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    // Distinguish between a props update versus a simple state update
    if (prevParentElement === nextParentElement) {
      // Skip checking prop types again -- we don't read inst.props to avoid
      // warning for DOM component props in this upgrade
      nextProps = nextParentElement.props;
    } else {
      nextProps = this._processProps(nextParentElement.props);
      willReceive = true;
    }

    // An update here will schedule an update but immediately set
    // _pendingStateQueue which will ensure that any state updates gets
    // immediately reconciled instead of waiting for the next batch.
    if (willReceive && inst.componentWillReceiveProps) {
      inst.componentWillReceiveProps(nextProps, nextContext);
    }

    var nextState = this._processPendingState(nextProps, nextContext);

    var shouldUpdate =
      this._pendingForceUpdate ||
      !inst.shouldComponentUpdate ||
      inst.shouldComponentUpdate(nextProps, nextState, nextContext);

    if (process.env.NODE_ENV !== "production") {
      process.env.NODE_ENV !== "production"
        ? warning(
            shouldUpdate !== undefined,
            "%s.shouldComponentUpdate(): Returned undefined instead of a " +
              "boolean value. Make sure to return true or false.",
            this.getName() || "ReactCompositeComponent",
          )
        : void 0;
    }

    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      // Will set `this.props`, `this.state` and `this.context`.
      this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext,
      );
    } else {
      // If it's determined that a component should not update, we still want
      // to set props and state but we shortcut the rest of the update.
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
  },

  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(
        nextState,
        typeof partial === "function"
          ? partial.call(inst, nextState, props, context)
          : partial,
      );
    }

    return nextState;
  },

  /**
   * Merges new props and state, notifies delegate methods of update and
   * performs update.
   *
   * @param {ReactElement} nextElement Next element
   * @param {object} nextProps Next public object to set as properties.
   * @param {?object} nextState Next object to set as state.
   * @param {?object} nextContext Next public object to set as context.
   * @param {ReactReconcileTransaction} transaction
   * @param {?object} unmaskedContext
   * @private
   */
  _performComponentUpdate: function (
    nextElement,
    nextProps,
    nextState,
    nextContext,
    transaction,
    unmaskedContext,
  ) {
    var inst = this._instance;

    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
      prevProps = inst.props;
      prevState = inst.state;
      prevContext = inst.context;
    }

    if (inst.componentWillUpdate) {
      inst.componentWillUpdate(nextProps, nextState, nextContext);
    }

    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    this._updateRenderedComponent(transaction, unmaskedContext);

    if (hasComponentDidUpdate) {
      transaction
        .getReactMountReady()
        .enqueue(
          inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
          inst,
        );
    }
  },

  /**
   * Call the component's `render` method and update the DOM accordingly.
   *
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  _updateRenderedComponent: function (transaction, context) {
    var prevComponentInstance = this._renderedComponent;
    var prevRenderedElement = prevComponentInstance._currentElement;
    var nextRenderedElement = this._renderValidatedComponent();
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      ReactReconciler.receiveComponent(
        prevComponentInstance,
        nextRenderedElement,
        transaction,
        this._processChildContext(context),
      );
    } else {
      var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance);
      ReactReconciler.unmountComponent(prevComponentInstance, false);

      this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedComponent =
        this._instantiateReactComponent(nextRenderedElement);
      var nextMarkup = ReactReconciler.mountComponent(
        this._renderedComponent,
        transaction,
        this._nativeParent,
        this._nativeContainerInfo,
        this._processChildContext(context),
      );
      this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
    }
  },

  /**
   * Overridden in shallow rendering.
   *
   * @protected
   */
  _replaceNodeWithMarkup: function (oldNativeNode, nextMarkup) {
    ReactComponentEnvironment.replaceNodeWithMarkup(oldNativeNode, nextMarkup);
  },

  /**
   * @protected
   */
  _renderValidatedComponentWithoutOwnerOrContext: function () {
    var inst = this._instance;
    var renderedComponent = inst.render();
    if (process.env.NODE_ENV !== "production") {
      // We allow auto-mocks to proceed as if they're returning null.
      if (renderedComponent === undefined && inst.render._isMockFunction) {
        // This is probably bad practice. Consider warning here and
        // deprecating this convenience.
        renderedComponent = null;
      }
    }

    return renderedComponent;
  },

  /**
   * @private
   */
  _renderValidatedComponent: function () {
    var renderedComponent;
    ReactCurrentOwner.current = this;
    try {
      renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext();
    } finally {
      ReactCurrentOwner.current = null;
    }
    !(
      // TODO: An `isValidNode` function would probably be more appropriate
      (
        renderedComponent === null ||
        renderedComponent === false ||
        ReactElement.isValidElement(renderedComponent)
      )
    )
      ? process.env.NODE_ENV !== "production"
        ? invariant(
            false,
            "%s.render(): A valid React element (or null) must be returned. You may have " +
              "returned undefined, an array or some other invalid object.",
            this.getName() || "ReactCompositeComponent",
          )
        : invariant(false)
      : void 0;
    return renderedComponent;
  },

  /**
   * Lazily allocates the refs object and stores `component` as `ref`.
   *
   * @param {string} ref Reference name.
   * @param {component} component Component to store as `ref`.
   * @final
   * @private
   */
  attachRef: function (ref, component) {
    var inst = this.getPublicInstance();
    !(inst != null)
      ? process.env.NODE_ENV !== "production"
        ? invariant(false, "Stateless function components cannot have refs.")
        : invariant(false)
      : void 0;
    var publicComponentInstance = component.getPublicInstance();
    if (process.env.NODE_ENV !== "production") {
      var componentName =
        component && component.getName ? component.getName() : "a component";
      process.env.NODE_ENV !== "production"
        ? warning(
            publicComponentInstance != null,
            "Stateless function components cannot be given refs " +
              '(See ref "%s" in %s created by %s). ' +
              "Attempts to access this ref will fail.",
            ref,
            componentName,
            this.getName(),
          )
        : void 0;
    }
    var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
    refs[ref] = publicComponentInstance;
  },

  /**
   * Detaches a reference name.
   *
   * @param {string} ref Name to dereference.
   * @final
   * @private
   */
  detachRef: function (ref) {
    var refs = this.getPublicInstance().refs;
    delete refs[ref];
  },

  /**
   * Get a text description of the component that can be used to identify it
   * in error messages.
   * @return {string} The name or null.
   * @internal
   */
  getName: function () {
    var type = this._currentElement.type;
    var constructor = this._instance && this._instance.constructor;
    return (
      type.displayName ||
      (constructor && constructor.displayName) ||
      type.name ||
      (constructor && constructor.name) ||
      null
    );
  },

  /**
   * Get the publicly accessible representation of this component - i.e. what
   * is exposed by refs and returned by render. Can be null for stateless
   * components.
   *
   * @return {ReactComponent} the public component instance.
   * @internal
   */
  getPublicInstance: function () {
    var inst = this._instance;
    if (inst instanceof StatelessComponent) {
      return null;
    }
    return inst;
  },

  // Stub
  _instantiateReactComponent: null,
};

ReactPerf.measureMethods(
  ReactCompositeComponentMixin,
  "ReactCompositeComponent",
  {
    mountComponent: "mountComponent",
    updateComponent: "updateComponent",
    _renderValidatedComponent: "_renderValidatedComponent",
  },
);

var ReactCompositeComponent = {
  Mixin: ReactCompositeComponentMixin,
};

module.exports = ReactCompositeComponent;
